﻿using System.Threading;
using DM4;
using sscore;
using System;
using System.Collections.Generic;
using sscore.Utils;
using System.Text.RegularExpressions;
using System.IO;
using sscore.Requests;
using System.Collections.Specialized;

namespace MN
{
    public class MN : ModuleBase
    {
        private readonly string _searchUrl;
        private readonly string _offenderLink;

        private string _imageLink;

        private const string StateName = "MN";

        private ResultFile _imagesResultFile;


        public MN()
        {
            //initialize main system constants
            ModuleCode = "SO_MN";
            Description = "MN Sex Offenders";
            Version = "1.0";

            //for creating sessions
            StartParam = 0;
            StopParam = 1000;

            //initialize help 
            AddToHelp(DM4Consts.StartPosition, String.Format("{0}...{1}", 0, StopParam));
            AddToHelp(DM4Consts.EndPositon, String.Format("{0}...{1}", 0, StopParam));

            AddToHelp(DM4Consts.IsDownload, "true|false");
            AddToHelp(DM4Consts.IsDownloadImg, "true|false");
            AddToHelp(DM4Consts.InsertToDb, "false|true", "(Expected: servername, dbname)");
            AddToHelp(DM4Consts.ServerName, "");
            AddToHelp(DM4Consts.DbName, "");
            AddToHelp(DM4Consts.IsClearDb, "false|true");
            AddToHelp(DM4Consts.TestParam, "false|true");
            
            RootUrl = "http://www.doc.state.mn.us/";
            _searchUrl = RootUrl + "level3/SearchResults.asp?SearchType=Name&FirstName=&LastName=&Submit2=go";
            _offenderLink = RootUrl + "level3/OffenderDetail.asp?OID={0}";
        }

        private void SetParametersDefaults()
        {
            Params.CreateBooleanParam(DM4Consts.IsDownload, true); //default value is true        
            Params.CreateBooleanParam(DM4Consts.IsDownloadImg, true); //default value is true        
            Params.CreateBooleanParam(DM4Consts.InsertToDb, false); //default value is false       
            Params.CreateBooleanParam(DM4Consts.IsClearDb, false); //default value is false        
            Params.CreateStringParam(DM4Consts.DbName, ""); //default value is ""    
            Params.CreateStringParam(DM4Consts.ServerName, ""); //default value is ""       
            Params.CreateIntegerParam(DM4Consts.StartPosition, 0); //default value is 0        
            Params.CreateIntegerParam(DM4Consts.LastPosition, Params.GetIntegerParam(DmConsts.StartPosition)); //default value is DmConsts.StartPosition param value :)        
            Params.CreateIntegerParam(DM4Consts.EndPositon, StopParam); //default value is 0
        }

        public override OperationResult Init(IDM4Core iCore, DmTaskParams param)
        {
            //use basic initialization sequence
            base.Init(iCore, param);

            //set defaults for all possible parameters
            SetParametersDefaults();

            Rpm.Codepage = EncodingType.Iso88591;

            //do any initializations here            
            return new OperationResult { Result = DM4Consts.Ok };
        }

        public override OperationResult Start()
        {
            DoWork();
            return new OperationResult { Result = DM4Consts.Ok };
        }

        public override OperationResult DoWork()
        {
            OperationResult or = new OperationResult { Result = DM4Consts.ErrorUnknown };

            //check if need abort task
            if (!Ct.IsCancellationRequested)
            {
                WriteLog("Start parameters:", MessageType.Warning);
                foreach (KeyValuePair<string, string> kvp in Params.GetDictionary()) //get all existing parameters
                    WriteLog(String.Format("Key = {0}, Value = {1}", kvp.Key, kvp.Value), MessageType.Warning);

                if (Params.GetBooleanParam(DM4Consts.IsDownload))
                    Load();

                if (Params.GetBooleanParam(DM4Consts.InsertToDb))
                    if ((Params.GetStringParam(DM4Consts.DbName) == "") || (Params.GetStringParam(DM4Consts.ServerName) == ""))
                    {
                        or.Message = String.Format("{0} or {1} is empty", DM4Consts.DbName, DM4Consts.ServerName);
                        ResultCode = DM4Consts.SessionResultCodeError;
                        ResultMessage = or.Message;
                        UpdateStatus(DmConsts.Error);
                        WriteErrorLog(or.Message); //writting to error and session log   
                        return or;
                    }
                    else
                    {
                        //insert offenders to db 
                        UpdateStatus(DM4Consts.SessionInsertToDb);

                        Offenders.OffInsertToDatabase(StateName, Params.GetStringParam(DmConsts.DbName),
                            Params.GetStringParam(DmConsts.ServerName), ResultDir, Params.GetBooleanParam(DmConsts.IsClearDb),
                            WriteLog);

                    }

                //check if need abort task
                if (Ct.IsCancellationRequested)
                {
                    ResultCode = DM4Consts.SessionResultTerminateByUser;
                    ResultMessage = "Session terminated by user";
                    UpdateStatus(DM4Consts.SessionWait);
                    WriteLog("Session aborted by user", MessageType.Cool);
                }
                else
                {
                    UpdateProgress(1);
                    ResultCode = DM4Consts.SessionResultCodeOk;
                    ResultMessage = String.Format("Session complete, errors : {0}, records : {1}", TaskParams.ErrorCount, TaskParams.RecordCount);
                    UpdateStatus(DM4Consts.SessionComplete);
                    WriteLog("------------Finish----------------", MessageType.Cool);
                }
            }
            else
            {
                ResultCode = DM4Consts.SessionResultTerminateByUser;
                ResultMessage = "Session terminated by user";
                UpdateStatus(DM4Consts.SessionWait);
                WriteLog("Session aborted by user", MessageType.Cool);
            }

            return or;
        }

        private void Load()
        {
            string resultFileName = String.Format("{0}\\{1}.{2}", ResultDir, TaskParams.SessionId, DmConsts.ResultFilesExtension);
            bool workFlag = true;

            ResultFile = new ResultFile(resultFileName, OffendersResult.GetHeader());
            _imagesResultFile = new ResultFile(String.Format("{0}\\{1}_images.{2}", ResultDir, TaskParams.SessionId, DM4Consts.ResultFilesExtension), "FileName|FileUrl");
            try
            {
                while (workFlag)
                {
                    //рабочий цикл
                    string error;

                    try
                    {
                        error = "";  //Required!!  /*if (!String.IsNullOrEmpty(error)) --> restart thread; else --> download finished*/

                        WriteLog("Start download...");
                        Searching();
                    }
                    catch (Exception e)
                    {
                        error = e.Message;
                    }

                    if (!String.IsNullOrEmpty(error))
                    {
                        Thread.Sleep(30000);
                        WriteErrorLog(error);
                        WriteLog("Try continue work...", MessageType.Warning);
                    }
                    else
                    {
                        workFlag = false;  //все нормально откачало, заканчиваем работу
                    }
                }
            }
            finally
            {
                ResultFile.Close();
                _imagesResultFile.Close();
            }
        }

        private void Searching()
        {
            if (Test)
            {
                Params.SetIntegerParam(DmConsts.LastPosition, Params.GetIntegerParam(DmConsts.StartPosition));
                Params.SetIntegerParam(DmConsts.EndPositon, Params.GetIntegerParam(DmConsts.StartPosition));
            }

            string html = GetRequest.UrlOnly(_searchUrl, Rpm);

            if (!html.Contains("Your search returned the following Level 3 predatory offenders"))
                throw new Exception(DmConsts.ExcNeedRestart);

            MatchCollection mc = RegExpr.ParseMatchCollection(html, @"(?si:OffenderDetail.asp\x3FOID=([\S]+)\x27>)");
            WriteLog(String.Format("[{0}] matches", mc.Count));

            StringCollection lstIds = new StringCollection();
            foreach (Match matchLoc in mc)
                if (!lstIds.Contains(matchLoc.Groups[1].Value.Trim()))
                    lstIds.Add(matchLoc.Groups[1].Value.Trim());
            WriteLog(String.Format("[{0}] offenders IDs found", lstIds.Count));

            if (Params.GetIntegerParam(DmConsts.EndPositon) > (lstIds.Count - 1))
                Params.SetIntegerParam(DmConsts.EndPositon, lstIds.Count - 1);


            for (int indSearchParam = Params.GetIntegerParam(DmConsts.LastPosition); indSearchParam <= Params.GetIntegerParam(DmConsts.EndPositon); indSearchParam++)
            {
                if (Ct.IsCancellationRequested)
                    break;

                Params.SetIntegerParam(DmConsts.LastPosition, indSearchParam);
                UpdateProgress();

                string offenderHtml = GetRequest.UrlOnly(String.Format(_offenderLink, lstIds[indSearchParam]), Rpm);
                WriteLog(String.Format("ParseOffender [{0}][{1}]", Params.GetIntegerParam(DmConsts.LastPosition), lstIds[indSearchParam]));
                if (!offenderHtml.Contains("OID:"))
                    throw new Exception("Offender page: " + DmConsts.ExcNeedRestart);
                ParseOffender(offenderHtml, lstIds[indSearchParam]);

                UpdateRecordCount(1);

                Params.SetIntegerParam(DmConsts.LastPosition, indSearchParam);
            }

            Params.SetIntegerParam(DmConsts.LastPosition, 0);
        }

        private void ParseOffender(string offenderHtml, string lookupKey)
        {
            const string crimeCaption = "Statute; Information~";

            offenderHtml = HtmlUtils.KillSpace(offenderHtml.Replace(Environment.NewLine, "").Replace("\t", ""));

            using (OffendersResult or = new OffendersResult()
                {
                    Source_State = StateName,
                    Source_id = lookupKey,
                    offenderLink = String.Format(_offenderLink, lookupKey),
                    Status = "level 3 Predatory Offender"
                }
                )
            {

                if (Params.GetBooleanParam(DmConsts.IsDownloadImg))
                {
                    int iImg = 0;
                    MatchCollection mcImg = RegExpr.ParseMatchCollection(offenderHtml,
                            String.Format(@"(?si:showing.asp\x3FOID={0}\x26PhotoKey=([\S]+)\s*"")", or.Source_id));

                    if (mcImg.Count == 0)
                    {
                        mcImg = RegExpr.ParseMatchCollection(
                            RegExpr.Parse(offenderHtml, @"(?si:<td colspan=""2"" align=""center"">(.*?)</table>)"),
                            @"(?si:img[^<>]+src=""([^""]+)"")");

                        _imageLink = RootUrl + "level3/{0}";
                        foreach (Match m in mcImg)
                        {
                            string imgName;
                            if (iImg == 0)
                            {
                                or.Image_ID = Guid.NewGuid().ToString();
                                or.ImageLink = String.Format(_imageLink, m.Groups[1].Value);
                                imgName = String.Format("{0}\\{1}\\{2}.jpg", ResultDir, DmConsts.PhotosDir, or.Image_ID);
                            }
                            else
                                imgName = String.Format("{0}\\{1}\\{2}_{3}.jpg", ResultDir, DmConsts.PhotosDir, or.Image_ID, iImg);


                            try
                            {
                                Offenders.DownloadImageWithExceptions(_imagesResultFile, imgName, String.Format(_imageLink, m.Groups[1].Value), TaskParams.SessionId);
                                iImg++;
                            }
                            catch (Exception ex)
                            {
                                or.Image_ID = "";
                                or.ImageLink = "";

                                if (!ex.Message.Contains("Parameter is not valid"))
                                    throw new Exception("Error occured while downloading image.", ex);
                            }


                        }//foreach (Match m in mcImg)
                    }
                    else
                    {
                        _imageLink = RootUrl + "level3/showing.asp?OID={0}&PhotoKey={1}";
                        foreach (Match m in mcImg)
                        {
                            string imgName;
                            if (iImg == 0)
                            {
                                or.Image_ID = Guid.NewGuid().ToString();
                                or.ImageLink = String.Format(_imageLink, or.Source_id, m.Groups[1].Value);
                                imgName = String.Format("{0}\\{1}\\{2}.jpg", ResultDir, DmConsts.PhotosDir, or.Image_ID);
                            }
                            else
                                imgName = String.Format("{0}\\{1}\\{2}_{3}.jpg", ResultDir, DmConsts.PhotosDir, or.Image_ID, iImg);

                            try
                            {
                                or.Image_ID = "";
                                or.ImageLink = "";
                                Offenders.DownloadImageWithExceptions(_imagesResultFile, imgName, String.Format(_imageLink, or.Source_id, m.Groups[1].Value), TaskParams.SessionId);
                                iImg++;
                            }
                            catch (Exception ex)
                            {
                                if (!ex.Message.Contains("Parameter is not valid"))
                                    throw new Exception("Error occured while downloading image.", ex);
                            }
                        } //foreach (Match m in mcImg)
                    }
                }

                if (or.Image_ID != "") or.Image_ID += ".jpg";
                if (or.Image_ID != "" && !File.Exists(String.Format("{0}\\{1}\\{2}", ResultDir, DmConsts.PhotosDir, or.Image_ID)))
                    throw new Exception(DmConsts.ExcImgNotFound);

                or.fullname = RegExpr.Parse(offenderHtml, @"(?si:<td colspan=""2"" align=""center""><b>(.*?)</b>)").Trim();
                or.AKA = HtmlUtils.KillSpace(RegExpr.Parse(offenderHtml, @"(?si:Aliases\s*:\s*</b>\x26nbsp;\x26nbsp;</td>\s*<td>(.*?)</td>)").Replace("<br>", ";")).Trim();
                or.Address = HtmlUtils.KillSpace(RegExpr.Parse(offenderHtml, @"(?si:Registered Address\s*:\s*</b>&nbsp;&nbsp;</td>\s*<td>(.*?)</td>)").Replace("<br>", "@")).Trim();
                or.County = HtmlUtils.KillSpace(RegExpr.Parse(offenderHtml, @"(?si:<b>Address County\s*:\s*</b>(.*?)</td>)").Replace("&nbsp;", " ")).Trim();
                or.Birthdate = RegExpr.Parse(offenderHtml, @"(?si:Birth Date\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.Ethnicity = RegExpr.Parse(offenderHtml, @"(?si:Race/Ethnicity\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.skin = RegExpr.Parse(offenderHtml, @"(?si:Skin Tone\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.hair_color = RegExpr.Parse(offenderHtml, @"(?si:Hair Color\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.eye_color = RegExpr.Parse(offenderHtml, @"(?si:Eye Color\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.height = RegExpr.Parse(offenderHtml, @"(?si:Height\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();
                or.weight = RegExpr.Parse(offenderHtml, @"(?si:Weight\s*:\s*</b>\s*</td>\s*<td>(.*?)</td>)").Trim();

                or.Crime = crimeCaption;
                or.Crime += HtmlUtils.KillSpace(RegExpr.Parse(offenderHtml, @"(?si:Offense Statute\x28s\x29\s*:\s*</b>(.*?)</td>)").Replace("&nbsp;", " ")).Trim() + ";";
                or.Crime += HtmlUtils.KillSpace(RegExpr.Parse(offenderHtml, @"(?si:Offense Information\s*:\s*</b>\s*<br>(.*?)</td>)").Replace("&nbsp;", " ")).Trim() + "~";

                ResultFile.Write(or.GetValues());
            }
        }

        private void UpdateProgress()
        {
            if (Params.GetIntegerParam(DmConsts.StartPosition) > Params.GetIntegerParam(DmConsts.EndPositon))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.StartPosition), Params.GetIntegerParam(DmConsts.EndPositon),
                    DmConsts.StartPosition, DmConsts.EndPositon));
            if (Params.GetIntegerParam(DmConsts.LastPosition) > Params.GetIntegerParam(DmConsts.EndPositon))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.LastPosition), Params.GetIntegerParam(DmConsts.EndPositon),
                    DmConsts.StartPosition, DmConsts.EndPositon));
            if (Params.GetIntegerParam(DmConsts.StartPosition) > Params.GetIntegerParam(DmConsts.LastPosition))
                throw new Exception(String.Format("Invalid parameters {2}[{0}]; {3} [{1}]",
                    Params.GetIntegerParam(DmConsts.StartPosition), Params.GetIntegerParam(DmConsts.LastPosition),
                    DmConsts.StartPosition, DmConsts.LastPosition));
            UpdateProgress(((double)(Params.GetIntegerParam(DmConsts.LastPosition) - Params.GetIntegerParam(DmConsts.StartPosition))) / (Params.GetIntegerParam(DmConsts.EndPositon) - Params.GetIntegerParam(DmConsts.StartPosition) + 1));
        }
    }
}
